﻿using System;
using System.IO;
using System.Linq;

namespace Kangaroo.Model
{
    public class PrpsGraph
    {
        private readonly int PDRangeCount = 100;
        public PrpsGraph(byte[] rawData, bool encrypt = true)
        {
            var stream = new MemoryStream(rawData);
            using (var reader = new BinaryReader(stream))
            {
                Header = new PrpsHeader(reader);
                PeriodRange = Enumerable.Range(0, Header.YSize).ToArray();
                PhaseRange = Enumerable.Range(0, Header.XSize).Select(i => i * (360 / Header.XSize)).ToArray();
                PDRange = Enumerable.Range(0, PDRangeCount).Select(i => i / (float)PDRangeCount).ToArray(); // 需改为从配置文件读取
                PDCountPerPDRange = new int[PDRangeCount];
                if (Header.XSize > 100 || Header.YSize > 100 || Header.XSize < 0 || Header.YSize < 0)
                {
                    throw new InvalidDataException(string.Format("XSize {0} and YSize {1} are not valid.", Header.XSize, Header.YSize));
                }
                Data = new float[Header.XSize, Header.YSize];
                if (encrypt)
                {
                    int key = 221;
                    byte[] tmp = EncryptDecryptByteArray(reader.ReadBytes(Header.XSize * Header.YSize * 4), (byte)key);
                    for (var i = 0; i < Header.YSize; i++)
                    {
                        for (var j = 0; j < Header.XSize; j++)
                        {
                            Data[j, i] = BitConverter.ToSingle(tmp, i * Header.XSize * 4 + j * 4);
                            if (Data[j, i] > 0)
                            {
                                PDCountPerPDRange[(int)(Data[j, i] * PDRangeCount)]++;
                            }
                        }
                    }
                }
                else
                {
                    for (var i = 0; i < Header.YSize; i++)
                    {
                        for (var j = 0; j < Header.XSize; j++)
                        {
                            Data[j, i] = reader.ReadSingle();
                            if (Data[j, i] > 0)
                            {
                                PDCountPerPDRange[(int)(Data[j, i] * PDRangeCount)]++;
                            }
                        }
                    }
                }
            }
            PrepareData();
        }
        
        public int[] PeriodRange { get; private set; }

        public int[] PhaseRange { get; private set; }

        public float[] PDRange { get; private set; }

        public int[] PDCountPerPeriod { get; private set; }

        public int[] PDCountPerPDRange { get; private set; }

        public PrpsHeader Header { get; private set; }

        public float[,] Data { get; private set; }

        public float[] SumOfPeriod { get; private set; }

        public float[] MaxQPerPeriod { get; private set; }

        public float[] AverageOfPeriod { get; private set; }

        public float[] SumOfPhase { get; private set; }

        public float[] MaxQPerPhase { get; private set; }

        public float[] AverageOfPhase { get; private set; }

        private void PrepareData()
        {
            var phaseCount = Data.GetUpperBound(0) + 1; //相位分隔
            var periodCount = Data.GetUpperBound(1) + 1; //工频周期

            PreparePeriod(phaseCount, periodCount);

            PreparePhase(phaseCount, periodCount);
        }

        private void PreparePhase(int phaseCount, int periodCount)
        {
            SumOfPhase = new float[phaseCount];
            SumOfPhase.Initialize();
            MaxQPerPhase = new float[phaseCount];
            AverageOfPhase = new float[phaseCount];

            for (var i = 0; i < phaseCount; i++)
            {
                MaxQPerPhase[i] = float.NegativeInfinity;
                for (var j = 0; j < periodCount; j++)
                {
                    var dataItem = Data[i, j];
                    SumOfPhase[i] += dataItem;
                    MaxQPerPhase[i] = Math.Max(MaxQPerPhase[i], dataItem);
                }
                AverageOfPhase[i] = SumOfPhase[i] / periodCount;
            }
        }

        private void PreparePeriod(int phaseCount, int periodCount)
        {
            SumOfPeriod = new float[periodCount];
            SumOfPeriod.Initialize();

            MaxQPerPeriod = new float[periodCount];
            AverageOfPeriod = new float[periodCount];
            PDCountPerPeriod = new int[periodCount];
            PDCountPerPeriod.Initialize();
            for (int i = 0; i < periodCount; i++)
            {
                MaxQPerPeriod[i] = float.NegativeInfinity;
                for (int j = 0; j < phaseCount; j++)
                {
                    var dataItem = Data[j, i];
                    SumOfPeriod[i] += dataItem;
                    MaxQPerPeriod[i] = Math.Max(MaxQPerPeriod[i], dataItem);
                    if (dataItem > 0)
                        PDCountPerPeriod[i]++;
                }
                AverageOfPeriod[i] = SumOfPeriod[i] / phaseCount;
            }
        }


        private static byte[] EncryptDecryptByteArray(byte[] bs, byte key)
        {
            for (int i = 0; i < bs.Length; i++)
            {
                bs[i] = (byte)(bs[i] ^ key);
            }
            return bs;
        }

        #region Nested type: PrpsHeader

        public class PrpsHeader
        {
            #region GraphType enum

            public enum GraphType
            {
                Prps = 0x1,
                Prpd = 0x2
            }

            #endregion

            public PrpsHeader(BinaryReader reader)
            {
                Type = (GraphType) reader.ReadUInt32();
                XSize = reader.ReadUInt16();
                YSize = reader.ReadUInt16();

                // TODO: read remaining fields
                reader.ReadUInt16();
                reader.ReadInt16();
                reader.ReadUInt16();
                reader.ReadInt16();
                reader.ReadUInt16();
                reader.ReadInt16();
                reader.ReadSingle();
                reader.ReadSingle();
                reader.BaseStream.Seek(4, SeekOrigin.Current);
            }

            public GraphType Type { get; set; }

            public UInt16 XSize { get; set; }

            public UInt16 YSize { get; set; }

        }

        #endregion
    }
}